import React, {useEffect, useLayoutEffect, useRef, useState} from 'react';
import CarouselContext from './CarouselContext';
import PropTypes from 'prop-types';
import styles from './style.module.scss';

const INIT_TARGET_ACTION = {
	action: '',
	lastX: 0,
	lastY: 0,
	moveDistX: 0,
	preIndex: -1,
	index: 0,
	nextIndex: -1,
	lastPoints: [],
	rootWidth: 0,
	rootHeight: 0,
	scale: 1,
	width: 0,
	height: 0,
	left: 0,
	top: 0
};
const MOVE_ACTION = 'carousel move';
const ZOOM_ACTIOM = 'carousel zoom';
const MOVE_MIN_DIST_X = 48;
const MAX_SCALE = 5;

const getDistance = (points) => {
	return Math.hypot(points[1].x - points[0].x, points[1].y - points[0].y);
};

const Carousel = (props) => {
	const [rootStyle, setRootStyle] = useState({
		height: props.height,
		width: props.width
	});
	const items = useRef([]);

	const pushItem = (item) => {
		return items.current.push(item) - 1;
	};
	const refEl = useRef();
	const targetAction = useRef(INIT_TARGET_ACTION);

    const allowMoving = () => {
		return targetAction.current.action === MOVE_ACTION;
	};

	const allowZooming = () => {
		return targetAction.current.action === ZOOM_ACTIOM;
	};

	const changeIndex = () => {
		if (typeof props.onChange === 'function') {
			props.onChange(targetAction.current.index);
		}
	};

    const updateSiblingIndex = (newIndex) => {
		const lastItemIndex = items.current.length - 1;
		// 如果只有一个子集
		if (lastItemIndex < 1) {
			targetAction.current = {
				...targetAction.current,
				preIndex: -1,
				index: 0,
				nextIndex: -1
			};
			return;
		}
		let {index} = targetAction.current;
		if (newIndex === undefined) {
			newIndex = index;
		}
		if (!props.loop && (newIndex < 0 || newIndex > lastItemIndex)) {
			return;
		}
		// 重置index，index必须是明确的
		if (newIndex < 0) {
			index = lastItemIndex;
		} else if (newIndex > lastItemIndex) {
			index = 0;
		} else {
			index = newIndex;
		}

		targetAction.current = {
			...targetAction.current,
			preIndex: index - 1,
			index,
			nextIndex: index + 1
		};
	};

	const moveStartPosition = (index, x) => {
		if (index < 0 || index > items.current.length - 1) {
			return;
		}
		const {scale, top} = targetAction.current;
		items.current[index].startPosition(scale, x, top);
	};

    const moveSibling = (distX, distY) => {
		const length = items.current.length;
		if (length <= 1) {
			return;
		}
		const {preIndex, nextIndex} = targetAction.current;
		if (preIndex >= 0) {
			items.current[preIndex].move(distX, distY);
		}
		if (nextIndex !== preIndex && nextIndex < length) {
			items.current[nextIndex].move(distX, distY);
		}
	};

	const moveEndPosition = (index, x) => {
		if (index < 0 || index > items.current.length - 1) {
			return;
		}
		items.current[index].endPosition(x, targetAction.current.top);
	};

	const isEffectSibling = () => {
		const {rootWidth, left, width, index: curIndex} = targetAction.current;
		let allowChange = true;
		const edgeX = rootWidth - width;
		if (left >= 0) {
			allowChange = curIndex > 0;
		} else if (left <= edgeX) {
			allowChange = curIndex < items.current.length - 1;
		} else {
			allowChange = false;
		}
		return allowChange;
	};

    const moveStart = (clientX, clientY) => {
		targetAction.current = {
			...targetAction.current,
			action: MOVE_ACTION,
			lastX: clientX,
			lastY: clientY,
			moveDistX: 0
		};
		updateSiblingIndex();
		const {index, preIndex, nextIndex, width, left} = targetAction.current;
		moveStartPosition(index, left);
		moveStartPosition(preIndex, -width);
		moveStartPosition(nextIndex, width);
	};
    const moving = (clientX, clientY) => {
		if (!allowMoving()) {
			return;
		}
		let {lastX, moveDistX, lastY, left, top, index} = targetAction.current;
		const distX = clientX - lastX;
		lastX = clientX;
		moveDistX += distX;
		left += distX;

		const distY = clientY - lastY;
		lastY = clientY;
		top += distY;

		targetAction.current = {
			...targetAction.current,
			lastX,
			moveDistX,
			lastY,
			left,
			top
		};
		items.current[index].move(distX, distY);
		if (!isEffectSibling()) {
			return;
		}
		moveSibling(distX, distY);
	};
    const moveEnd = () => {
		if (allowMoving()) {
			const {moveDistX, rootWidth, rootHeight, top, left, width, height, index: curIndex} = targetAction.current;
			let newPosX = left;
			let newPosY = top;
			let allowChange = isEffectSibling();
			const edgeX = rootWidth - width;
			const edgeY = rootHeight - height;
			if (left >= 0) {
				newPosX = 0;
			} else if (left <= edgeX) {
				newPosX = edgeX;
			}

			if (top >= 0) {
				newPosY = 0;
			} else if (top <= edgeY) {
				newPosY = edgeY;
			}
			targetAction.current = {
				...targetAction.current,
				action: '',
				moveDistX: 0,
				lastX: 0,
				lastY: 0,
				top: newPosY,
				left: newPosX
			};

			// 当前并没有一定出边界，不需要调整相邻的元素
			if (moveDistX === 0 || !allowChange) {
				moveEndPosition(curIndex, newPosX);
				return;
			}

			// 需要切换当前index，相邻的index结束位置
			if (moveDistX > MOVE_MIN_DIST_X) {
				updateSiblingIndex(curIndex - 1);
				changeIndex();
			} else if (moveDistX < -MOVE_MIN_DIST_X) {
				updateSiblingIndex(curIndex + 1);
				changeIndex();
			}
			
			const {index, preIndex, nextIndex} = targetAction.current;
			if (newPosX < 0) {
				newPosX = 0;
			} else {
				newPosX = edgeX;
			}
			targetAction.current.left = newPosX;
			moveEndPosition(index, newPosX);

            if (moveDistX > 0) {
				moveEndPosition(nextIndex, width);
				if (moveDistX < MOVE_MIN_DIST_X) {
					moveEndPosition(preIndex, -width);
				}
			} else if (moveDistX < 0) {
				moveEndPosition(preIndex, -width);
				if (moveDistX > -MOVE_MIN_DIST_X) {
					moveEndPosition(nextIndex, width);
				}
			}
		} else if (allowZooming()) {
			targetAction.current = {
				...targetAction.current,
				action: '',
				lastPoints: []
			};
		}
	};

    const zoomStart = (touches) => {
		targetAction.current = {
			...targetAction.current,
			action: ZOOM_ACTIOM,
			lastPoints: [
				{x: touches[0].clientX, y: touches[0].clientY},
				{x: touches[1].clientX, y: touches[1].clientY}
			]
		};
	};
    const zooming = (touches) => {
		if (!allowZooming()) {
			return;
		}
		const curPoints = [
			{x: touches[0].clientX, y: touches[0].clientY},
			{x: touches[1].clientX, y: touches[1].clientY}
		];
		let {
			scale,
			rootWidth,
			rootHeight,
			width,
			height,
			left,
			top,
			lastPoints,
			index
		} = targetAction.current;
		const lastScale = scale;
		scale = Math.min(
			MAX_SCALE,
			Math.max(
				1,
				lastScale * (getDistance(curPoints) / getDistance(lastPoints))
			)
		);
		const x = (curPoints[0].x + curPoints[1].x) / 2;
		const y = (curPoints[0].y + curPoints[1].y) / 2;
		const distScale = scale - lastScale;
		const distLeft = distScale * rootWidth * ((x - left) / width);
		const distTop = distScale * rootHeight * ((y - top) / height);
		left -= distLeft;
		top -= distTop;
		lastPoints = curPoints;
		targetAction.current = {
			...targetAction.current,
			scale,
			lastPoints,
			left,
			top,
			width: rootWidth * scale,
			height: rootHeight * scale
		};
		items.current[index].zoom(scale, left, top);
	};
    const onMouseStart = (event) => {
		moveStart(event.clientX, event.clientY);
	};
	const onMouseMove = (event) => {
		if (allowMoving()) {
			moving(event.clientX, event.clientY);
		}
	};
    const onTouchStart = (event) => {
		// TO DO 要看下手机双指触碰上来的时候，触发了几次
		console.log(event);
		if (event.touches.length === 1) {
			moveStart(event.touches[0].clientX, event.touches[0].clientY);
		} else if (event.touches.length > 1) {
			zoomStart(event.touches);
		}
	};
	const onTouchMove = (event) => {
		event.stopPropagation();
		event.preventDefault();
		if (allowMoving()) {
			moving(event.touches[0].clientX, event.touches[0].clientY);
			return;
		}
		if (allowZooming()) {
			zooming(event.touches);
		}
	};

    useEffect(() => {
		const width = props.width || refEl.current.offsetWidth;
		const {scale} = targetAction.current;
		setRootStyle((preStyle) => ({
			...preStyle,
			width
		}));
		targetAction.current = {
			...targetAction.current,
			rootWidth: width,
			rootHeight: props.height,
			width: width * scale,
			height: props.height * scale
		};
	}, []);
    const registerEvent = (destory = false) => {
		if (destory) {
			refEl.current.removeEventListener('mousedown', onMouseStart, false);
			refEl.current.removeEventListener('mousemove', onMouseMove, false);
			refEl.current.removeEventListener('mouseup', moveEnd, false);
			refEl.current.removeEventListener('mouseout', moveEnd, false);
			refEl.current.removeEventListener('touchstart', onTouchStart, false);
			refEl.current.removeEventListener('touchmove', onTouchMove, false);
			refEl.current.removeEventListener('touchend', moveEnd, false);
			refEl.current.removeEventListener('touchcancel', moveEnd, false);
		} else {
			refEl.current.addEventListener('mousedown', onMouseStart, false);
			refEl.current.addEventListener('mousemove', onMouseMove, false);
			refEl.current.addEventListener('mouseup', moveEnd, false);
			refEl.current.addEventListener('mouseout', moveEnd, false);
			refEl.current.addEventListener('touchstart', onTouchStart, false);
			refEl.current.addEventListener('touchmove', onTouchMove, false);
			refEl.current.addEventListener('touchend', moveEnd, false);
			refEl.current.addEventListener('touchcancel', moveEnd, false);
		}
	};
    useLayoutEffect(() => {
		registerEvent();
		return () => {
			registerEvent(true);
		};
	}, [props.onChange]);
    useLayoutEffect(() => {
		const {activeIndex} = props;
		if (
			activeIndex > -1 &&
			activeIndex < items.current.length &&
			activeIndex !== targetAction.current.index
		) {
			moveStartPosition(
				targetAction.current.index,
				targetAction.current.rootWidth
			);
			targetAction.current = {
				...targetAction.current,
				action: '',
				index: activeIndex,
				moveDistX: 0
			};
			moveEndPosition(targetAction.current.index, 0);
			updateSiblingIndex();
		}
	}, [props.activeIndex]);

    return (
		<div className={styles.carousel} ref={refEl} style={rootStyle}>
			<CarouselContext.Provider
				value={{
					pushItem,
					rootWidth: rootStyle.width,
					rootHeight: rootStyle.height
				}}>
				{props.children}
			</CarouselContext.Provider>
		</div>
	);
};

Carousel.propTypes = {
	height: PropTypes.number,
	width: PropTypes.number,
	children: PropTypes.any,
	activeIndex: PropTypes.number,
	loop: PropTypes.bool,
	onChange: PropTypes.func
};

Carousel.defaultProps = {
	height: 200,
	width: 0,
	activeIndex: 0,
	loop: true
};

export default Carousel;
